
/******************************************************************************
 * MODULE     : QTMTreeModel.hpp
 * DESCRIPTION: A QAbstractItemModel based on TeXmacs trees.
 * COPYRIGHT  : (C) 2013 Miguel de Benito Delgado
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "QTMTreeModel.hpp"
#include "QTMGuiHelper.hpp"

#include "modification.hpp"
#include "observers.hpp"
#include "path.hpp"
#include "tm_url.hpp"

#include "qt_utilities.hpp"

#include <QPixmap>

const char* QTMTreeModel::bad_tree_data_exception=
    "QTMTreeModel: couldn't parse provided data";
const char* QTMTreeModel::no_observer_exception=
    "QTMTreeModel: tree lacks tree pointer or inverse path observer";

/*! Creates a QTMTreeModel attached to a given tree or returns the one already
    attached if present.

 See the documentation for QTMTreeModel for the format for data roles.

 @note The tree pointer is marked const because we don't retain the tree_rep
       inside. However, we do attach observers to the tree, so maybe it
       shouldn't be const after all.
 */
QTMTreeModel*
QTMTreeModel::instance (const tree& data, const tree& roles, QObject* parent) {
  tree& t= const_cast<tree&> (data);
  if (!is_nil (data) && !is_nil (t->data) &&
      t->data->get_type () == OBSERVER_WIDGET)
    return static_cast<qt_tree_observer_rep*> (t->data.rep)->model;
  else return new QTMTreeModel (data, roles, parent);
}

/*! Internal construction of QTMTreeModels

 Construction of QTMTreeModels is done using QTMTreeModel::instance () to ensure
 only one model is built and attached to a given tree. This is done internally
 with a qt_tree_observer attached to trees. This observer takes ownership of
 the QTMTreeModel.
 */
QTMTreeModel::QTMTreeModel (const tree& data, const tree& roles, QObject* p)
    : QAbstractItemModel (p), _t_rep (inside (data)) {
  tree t= tree (_t_rep);
  parse_roles (roles);
  const path& ip= obtain_ip (t);
  if (is_nil (ip) || const_cast<path&> (ip)->item == DETACHED)
    attach_ip (t, path (0));

  // the qt_tree_observer takes ownership of this object
  attach_observer (t, qt_tree_observer (this));
}

QTMTreeModel::~QTMTreeModel () {
  // We must not detach our observers here: the qt_tree_observer_rep is the
  // owner of this object: if we are here, it is dying
}

inline int
QTMTreeModel::row_offset (const tree& t) const {
  return _roles[(inside (t))->op][NumberOfArguments];
}

tree
QTMTreeModel::item_from_index (const QModelIndex& index) const {
  if (!index.isValid ()) return tree (_t_rep);
  return tree (static_cast<tree_rep*> (index.internalPointer ()));
}

inline QModelIndex
QTMTreeModel::index_from_item (const tree& tref) const {
  tree& t = const_cast<tree&> (tref);
  tree  _t= tree (_t_rep);
  path  ip= obtain_ip (t);
  if (ipath_has_parent (ip)) {
    path p= reverse (ip->next) / reverse (obtain_ip (_t)); // Look one item up
    if (is_nil (p))
      return createIndex (ip->item, 0, inside (t)); // parent is root?
    tree& pt = subtree (_t, p);                     // pt is the parent
    int   row= ip->item - row_offset (pt);
    return createIndex (row, 0, inside (t));
  }
  return QModelIndex ();
}

bool
QTMTreeModel::hasIndex (int row, int column, const QModelIndex& parent) const {
  (void) column;
  const tree& t= item_from_index (parent);
  return is_compound (t) && row + row_offset (t) < N (t) && column == 0;
}

QModelIndex
QTMTreeModel::index (int row, int column, const QModelIndex& parent) const {
  if (!hasIndex (row, column, parent)) return QModelIndex ();
  tree t= item_from_index (parent);

  if (is_compound (t) && row + row_offset (t) < N (t))
    return createIndex (row, column, inside (t[row + row_offset (t)]));
  else return QModelIndex ();
}

QModelIndex
QTMTreeModel::parent (const QModelIndex& index) const {
  if (!index.isValid ()) return QModelIndex ();
  tree t = item_from_index (index);
  path ip= obtain_ip (t);
  if (ipath_has_parent (ip)) {
    path p= reverse (ip->next); // Look one item up in the path
    if (is_nil (p)) return QModelIndex ();
    tree _t = tree (_t_rep);
    p       = p / reverse (obtain_ip (_t));
    tree& pt= subtree (_t, p); // pt is the parent
    return index_from_item (pt);
  }
  return QModelIndex ();
}

int
QTMTreeModel::rowCount (const QModelIndex& parent) const {
  tree t= item_from_index (parent);
  return is_compound (t) ? N (t) - row_offset (t) : 0;
}

int
QTMTreeModel::columnCount (const QModelIndex& parent) const {
  (void) parent;
  return 1;
}

/// FIXME: this method is called quite often, maybe some (more?) caching is
// necessary
QVariant
QTMTreeModel::data (const QModelIndex& index, int role) const {
  const tree& tref= item_from_index (index);
  tree&       t   = const_cast<tree&> (tref);
  int         pos = _roles.contains (t->op) && _roles[t->op].contains (role)
                        ? _roles[t->op][role]
                        : -1;
  if (role >= TMUserRole && pos > -1 && !is_atomic (t) && N (t) >= pos &&
      is_atomic (t[pos]))
    return QVariant (to_qstring (t[pos]->label));

  switch (role) {
  case Qt::DisplayRole:
  case Qt::EditRole:
  case Qt::ToolTipRole:
  case Qt::StatusTipRole:
    if (is_atomic (t)) return to_qstring (t->label);
    else if (pos == -1 || !is_atomic (t[pos]))
      return to_qstring (as_string (L (t)));
    else return to_qstring (t[pos]->label);
  case Qt::DecorationRole: {
    // NOTE: By calling QPixmap's constructor with a file name, I'm bypassing
    // TeXmacs' caching of pictures in qt_load_xpm(), but since I need a
    // QPixmap and TeXmacs stores QImages this is preferable because Qt uses
    // QPixmapCache for all files it loads.
    // It might actually be better to cache the QIcons first and return them
    url u= "$TEXMACS_PIXMAP_PATH";
    if (pos == -1 || is_atomic (t) || !is_atomic (t[pos]))
      u= resolve ("$TEXMACS_PIXMAP_PATH" *
                  url ("treelabel-" * as_string (L (t)) * ".xpm"));
    else u= resolve (url (t[pos]->label));
    if (is_rooted_name (u)) return QPixmap (to_qstring (concretize (u)));
    else return QVariant ();
  } break;
  case CommandRole:
    if (pos > -1 && !is_atomic (t) && N (t) >= pos && is_atomic (t[pos]))
      return QVariant (to_qstring (t[pos]->label));
    else return QVariant ();
  case Qt::SizeHintRole:
  default:
    return QVariant ();
  }
  return QVariant ();
}

bool
QTMTreeModel::hasChildren (const QModelIndex& parent) const {
  tree t= item_from_index (parent);
  return is_compound (t) && N (t) > row_offset (t);
}

Qt::ItemFlags
QTMTreeModel::flags (const QModelIndex& index) const {
  (void) index;
  // FIXME: to do (plus draggable, etc.)
  return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}

QVariant
QTMTreeModel::headerData (int section, Qt::Orientation orient, int role) const {
  (void) section;
  (void) orient;
  (void) role;
  return QVariant ();
}

inline bool
QTMTreeModel::ipath_has_parent (const path& ip) const {
  // Careful: our root _t might be a subtree of some other tree in TeXmacs
  // already with inverse paths which lead further up the hierarchy
  // than we manage. That's why we need the extra check with obtain_ip()
  tree _t= tree (_t_rep);
  return !is_nil (ip) && const_cast<path&> (ip)->item != DETACHED &&
         obtain_ip (_t) != ip;
}

void
QTMTreeModel::parse_roles (const tree& roles) {
  tree& r= const_cast<tree&> (roles);

  if (!is_compound (roles)) return;

  for (int i= 0; i < N (roles); ++i) {
    if (is_compound (r[i])) {
      int tag                       = r[i]->op;
      _roles[tag][NumberOfArguments]= N (r[i]);
      for (int j= 0; j < N (r[i]); ++j) {
        ASSERT (is_atomic (r[i][j]), "QTMTreeModel: bad format declaration");
        string role= r[i][j]->label;
        if (role == "DisplayRole") _roles[tag][Qt::DisplayRole]= j;
        else if (role == "EditRole") _roles[tag][Qt::EditRole]= j;
        else if (role == "DecorationRole") _roles[tag][Qt::DecorationRole]= j;
        else if (role == "ToolTipRole") _roles[tag][Qt::ToolTipRole]= j;
        else if (role == "CommandRole") _roles[tag][CommandRole]= j;
        else if (string ("UserRole:") <= role) {
          int num= max (0, min (9, as_int (role (9, N (role))) - 1));
          _roles[tag][TMUserRole + num]= j;
        }
      }
    }
  }
}

/******************************************************************************
 * qt_tree_observer_rep
 ******************************************************************************/

qt_tree_observer_rep::~qt_tree_observer_rep () { delete model; }

void
qt_tree_observer_rep::announce (tree& ref, modification mod) {
  (void) ref;
  (void) mod;
  // if (mod->k != MOD_SET_CURSOR)
  //   model->beginResetModel();
  /* MOD_SET_CURSOR is announced only for TeXmacs buffers. We might want to also
   keep a pointer to a QItemSelectionModel, or maybe some QTMItemSelectionModel
   (which would allow for intermediate proxy models used by the views, as does
   KProxyItemSelectionModel (see
   http://steveire.wordpress.com/2010/04/20/sharing-a-qitemselection-between-views-through-proxy-models/
   ))

   In this case we could tell the views that they might want to update their
   selections, e.g.

     selection->setCurrentIndex (model->index_from_item (mod->t));

   Or if we have a QTMTreeSelectionModel:

     selection_model->set_cursor (ref, mod);

   and QTMTreeSelectionModel::set_cursor would do something like:

     if (current_item != mod->t) {
       current_item = mod->t;
       emit currentChanged (index_from_item (current_item));
     }

   The problem is we may not want all views to see their selections follow the
   cursor position. Furthermore, this only makes sense for buffers...
    */
}

void
qt_tree_observer_rep::done (tree& ref, modification mod) {
  (void) ref;
  (void) mod;
  // if (mod->k != MOD_SET_CURSOR)
  //   model->endResetModel();
}

observer
qt_tree_observer (QTMTreeModel* model) {
  return tm_new<qt_tree_observer_rep> (model);
}
